Le PL/SQL

Généralité	1
La notion de bloc PL/SQL (anonyme ou non)	1
Les types	1
Les variables et leur affectation	2
Les constantes	2
Les structures de contrôle	2
Les procédures et les fonctions	3
Les packages	5
La restitution de l’information	6
La récupération des valeurs d’un requête récupérant un seul enregistrement	6
Les curseurs	6
Les triggers basés	8
Les exceptions	9
L’auto indendation de la clé primaire	10
L’utilisation d’une fonction PL/SQL dans une requête SQL	10
Le SQL dynamique	10

Généralité
Le PL/SQL est un langage procédural, compilé, intégré au noyau Oracle. Il est intégré à la base de données (procédure ou fonction stockées dans la base) et le code est exécuté sur le serveur. Comme le langage Ada, les procédures et les fonctions peuvent être dans des packages constituant un ensemble logique (par exemple, le pakage codage va contenir toutes les fonctionnalités des codages).
Ce langage est non case sensitive, l’indentation est libre et les commentaires sont placés entre /* …. */ ou -– commentaire sur 1 ligne.
Il est possible d’y intégrer les instructions de manipulation de données dans le code PL/SQL. Cependant, il est possible de contourner le problème en utilisant le SQL dynamique.
La notion du curseur permet de lire enregistrement par enregistrement le résultat d’une requête.
Ce langage étant très riche, nous ne verrons que les principaux aspects de celui-ci.
La notion de bloc PL/SQL (anonyme ou non)
DECLARE
	… (optionnel)
BEGIN
	…
EXCEPTION
	… (optionnel)
END;

/ pour l’activer et me compiler
Afficher les erreurs de compilation SHOW ERRORS ou SHO ERR en SQL*Plus

Voici, le plus petit bloc PL/SQL anonyme : BEGIN NULL; END;
Les types
Ils sont identiques aux types d’Oracle

plus (liste non exhaustive):

- BOOLEAN (opérateur AND OR NOT)

- le type d’un champ (prend en compte les éventuelles modifications de type du champ d’où réduction du coût de maintenance)

ex : DEBIT CREDIT%TYPE; OU DEBIT USER.CREDIT%TYPE;

- le record

ex : TYPE MEETINGTYP IS RECORD (DAY DATE, NAME VARCHAR2(20), PURPOSE VARCHAR2(80));

- tous les types d’une table (prend en compte les éventuelles modifications de la table d’où réduction du coût de maintenance)

ex : EMP_REC EMP%ROWTYPE;

MY_DEPTNO := DEPT_REC.DEPTNO; -- POUR ACCÉDER AU CHAMP DEPTNO
Les variables et leur affectation
Dans le bloc de déclaration

BLOOD_TYPE CHAR := 'O';
VALID BOOLEAN := FALSE;

ou

BLOOD_TYPE CHAR DEFAULT 'O';
VALID BOOLEAN DEFAULT FALSE;
L’affectation
BONUS := CURRENT_SALARY * 0.10;
VALID := FALSE;
la non présence du non null
EMP_COUNT NATURAL NOT NULL := 0;
L’opérateur de concaténation de chaîne de caractères : ||
Les constantes
credit_limit CONSTANT REAL := 5000.00;
Les structures de contrôle
Le si

IF CHOICE = 1 THEN
…
END IF;

IF CHOICE <> ‘FIN’ THEN -- OU != OU ~=
…
ELSE
…
END IF;

IF VARIABLE IS NULL THEN ...

Une variante du si pour les si imbriqués

IF CHOICE = 1 THEN
…
ELSIF CHOICE = 4
…
END IF;

La structure algorithmique “selon » est disponible à partir de la version 9i.
Les boucles (itératives)

LOOP
	…
END LOOP;

Quitter une boucle

LOOP
...
TOTAL := TOTAL + SALARY;
EXIT WHEN TOTAL > 25000; -- EXIT LOOP IF CONDITION IS TRUE
END LOOP;
Le tant que

WHILE SALARY < 4000 LOOP
SELECT SAL, MGR, ENAME INTO SALARY, MGR_NUM, LAST_NAME FROM EMP WHERE EMPNO = MGR_NUM;
END LOOP;
La boucle 

FOR COUNTER IN [REVERSE] MIN..MAX LOOP – MIN <= MAX
SEQUENCE_OF_STATEMENTS;
END LOOP;

avec MIN < MAX

ex1 : Affichage de 1 à 5

FOR i IN 1..5 LOOP DBMS_OUTPUT.PUT_LINE(i); END LOOP;

ex2 : Affichage de 5 à 1

FOR i IN REVERSE 1..5 LOOP DBMS_OUTPUT.PUT_LINE(i); END LOOP;
Le branchement inconditionnel ( déconseillé !!!)

IF RATING > 90 THEN
GOTO BRANCHEMENT; -- BRANCH TO LABEL
END IF;

<<BRANCHEMENT>>
	…
Les procédures et les fonctions
Les procédures

Création d’une procédure

CREATE PROCEDURE NOM_PROC IS [bloc PL/SQL sans define] ; -- LE IS PEUT ETRE REMPLACE PAR AS
Ou
CREATE PROCEDURE NOM_PROC IS … [déclaration des variables] BEGIN … END NOM_PROC;

Modification d’une procédure : CREATE OR REPLACE PROCEDURE NOM_PROC IS IS [bloc PL/SQL sans define];

Destruction d’une procédure : DROP PROCEDURE NOM_PROC;

CREATE PROCEDURE NOM_PROC(EMP_ID NUMBER)
IS
 BONUS REAL;
BEGIN
	...
EXCEPTION
	...
END NOM_PROC;

Passage de paramètres par valeur, référence (en lecture écriture ou uniquement en écriture) : IN (valeur par défaut), OUT, IN OUT

CREATE PROCEDURE NOM_PROC(I OUT NUMBER; B IN FLOAT ) IS [bloc PL/SQL sans define] ; -- LE IS PEUT ETRE REMPLACE PAR AS
Les valeurs par défaut des paramètres

PROCEDURE CREATE_DEPT (P_NAME VARCHAR DEFAULT 'TEMP') IS [bloc PL/SQL sans define] ;
L’appel de la procédure

NOM(VAL);
NOM;

CREATE_DEPT;
CREATE_DEPT('MARKETING');
CREATE_DEPT('MARKETING', 'NEW YORK');
CREATE_DEPT( , 'NEW YORK'); -- INTERDIT
CREATE_DEPT(NEW_LOC => 'NEW YORK');
Sous SQL*Plus

EXECUTE CREATE_DEPT('FINANCE', 'NEW YORK');

ou

BEGIN CREATE_DEPT('FINANCE', 'NEW YORK'); END;
/

La notion de procédure externe existe. Celle-ci peut être codé en C par exemple.
Les fonctions

CREATE FUNCTION NOM_FONCTION (X FLOAT, Y IN OUT FLOAT) RETURN FLOAT
IS
-- DÉCLARATION
BEGIN
	…
	RETURN VAL1;
EXCEPTION
	...
END NOM_FONCTION;

Modification d’une fonction : CREATE OR REPLACE FUNCTION NOM_PROC IS [bloc PL/SQL sans define];

Destruction d’une fonction: DROP FUNCTION NOM_PROC;
L’appel de la fonction

VAL := NUM_FONC(…);
La récursivité

FUNCTION FAC (N POSITIVE) RETURN INTEGER IS -- RETURNS N!
BEGIN
IF N = 1 THEN -- TERMINATING CONDITION
		RETURN 1;
	ELSE
		RETURN N * FAC(N - 1); -- RECURSIVE CALL
	END IF;
END FAC
La surcharge ou l’overloading

Il est possible d’utiliser le même nom de fonction (ou de porocédure) si la signature de celle-ci i.e. le nombre de paramètres et leurs types est différente.
La portée des procédures et des fonctions

Elle reste classique. Il est possible d’encapsuler des fonctions ou des procédures dans des fonctions ou des procédures. Ci dessous, un exemple de fonction (la fonction B) encapsulée et donc utilisable que dans la procédure A.

PROCEDURE A (HIREDATE DATE) IS
I INTEGER;
	FUNCTION B (HIREDATE DATE) RETURN INTEGER IS 
	BEGIN 
		...
	END;
BEGIN
	I := NEW_HIRES('10-NOV-96');
	...
END;
Le prototypage des procédures et des fonctions 
 
La déclaration des fonctions ou des procédures permet de spécifier le code après l’avoir appelé. Dans ce cas, il convient en premier lieu de donner la signature de la fonction, i.e de prototyper la fonction ou la procédure.

Ex:

DECLARE
PROCEDURE CALC_RATING ( ... ); -- FORWARD DECLARATION
	...
PROCEDURE AWARD_BONUS ( ... ) IS
BEGIN
	CALC_RATING( ... );
	...
 END;

 PROCEDURE CALC_RATING ( ... ) IS 
 BEGIN 
	...
 END;
Les packages
Les packages correspondent à une encapsulation logique d’un ensemble de procédures et de fonctions. Certaines procédures et fonctions du package peuvent être visibles ou non, i.e. utilisables ou non de l’extérieur.

Les procédures et fonctions visibles sont prototypées dans la spécifications du package, tandis que celles privés ne seront spécifiées que dans le corps du package.

Deux notions existent donc : les spécifications et le corps

CREATE PACKAGE NAME AS – SPECIFICATION OU DECLARATION (VISIBLE PART)
-- PUBLIC TYPE AND ITEM DECLARATIONS
-- SUBPROGRAM SPECIFICATIONS
END [NAME];

CREATE PACKAGE BODY NAME AS -- BODY (HIDDEN PART)
-- PRIVATE TYPE AND ITEM DECLARATIONS
-- SUBPROGRAM BODIES
[BEGIN
-- INITIALIZATION STATEMENTS]
END [NAME];

Ex:

CREATE PACKAGE EMP_ACTIONS AS -- PACKAGE SPECIFICATION
PROCEDURE HIRE_EMPLOYEE (EMPNO NUMBER, ENAME CHAR, ...);
	PROCEDURE FIRE_EMPLOYEE (EMP_ID NUMBER);
END EMP_ACTIONS;

CREATE PACKAGE BODY EMP_ACTIONS AS -- PACKAGE BODY

PROCEDURE HIRE_EMPLOYEE (EMPNO NUMBER, ENAME CHAR, ...) IS – OR AS
BEGIN
INSERT INTO EMP VALUES (EMPNO, ENAME, ...);
END HIRE_EMPLOYEE;

PROCEDURE FIRE_EMPLOYEE (EMP_ID NUMBER) IS
BEGIN
DELETE FROM EMP WHERE EMPNO = EMP_ID;
END FIRE_EMPLOYEE;
END EMP_ACTIONS;

Modification d’un package (specification): CREATE OR REPLACE PACKAGE NOM_PKG AS …;
Modification d’un package (corps): CREATE OR REPLACE PACKAGE BODY NOM_PKG AS …;
Destruction d’un package (specification): DROP PACKAGE NOM_PKG AS …;
Destruction d’un package (corps): DROP PACKAGE BODY NOM_PKG AS …;
Recompilation: ALTER PACKAGE NOM_PKG COMPILE;

L’appel de procédure d’un package

Nom_package.nomproc
DBMS_OUTPUT.PUT_LINE(‘hello’);
Le package inclus d’origine (le package STANDARD)

Un package nommé STANDARD comportant les fonctions classiques existe. Par exemple, la fonction ABS qui retourne la valeur absolue.

Nb = STANDARD.ABS(x) ...

La plupart de ces fonctions sont surchargées comme la fonction TO_CHAR :

FUNCTION (RIGHT DATE) RETURN VARCHAR2;
FUNCTION TO_CHAR (LEFT NUMBER) RETURN VARCHAR2;
FUNCTION TO_CHAR (LEFT DATE, RIGHT VARCHAR2) RETURN VARCHAR2;
FUNCTION TO_CHAR (LEFT NUMBER, RIGHT VARCHAR2) RETURN VARCHAR2;
La restitution de l’information

Une notion de buffer existe et il n’y pas d’affichage au fil de l’eau. Le contenu du buffer s’affiche à la fin de l’exécution seulement si l’autorisation de l’affichage du buffer a été positionné. Activer le par SET SERVEROUTPUT ON ou OFF pour le désactiver.

DBMS_OUTPUT.PUT_LINE('hello');
DBMS_OUTPUT.PUT_LINE('hello il est'|| 10 ||’h’);

Le buffer possède une taille : SET SERVEROUTPUT ON SIZE n (2000  n 1000000)
La récupération des valeurs d’un requête récupérant un seul enregistrement
SELECT ID, NOM INTO VAR, VAR2 FROM CLI;
Les curseurs
Le curseur est obligatoire lorsque la requête est susceptible de retourner plus de 2 enregistrements. Le déplacement dans le curseur va permettre de lire enregistrement par enregistrement du début jusqu’à la fin. Le curseur peut comporter ou non des paramètres.
Pour les instructions insert, update, delete, le code SQL peut s’inclure directement dans un bloc PL/SQL.

Ex :
BEGIN
FOR NUM IN 1..500 LOOP
 		SERT INTO CLI VALUES (NUM, ‘DUPOND’);
 	ND LOOP;
END ;
/
Déclaration du curseur

Curseur sans paramètre : CURSOR EMP_CUR IS SELECT * FROM EMP WHERE IDREG =1;;
Curseur avec parameter(s) : CURSOR EMP_CUR(P_NAME VARCHAR, P_COMM NUMBER DEFAULT 0) IS SELECT * FROM EMP WHERE .NAME = P_NAME ….; 

Ex:
DECLARE
CURSOR MY_CURSOR(P_NB INTEGER) IS SELECT * FROM CAT WHERE ROWNUM < P_NB;
MY_REC MY_CURSOR%ROWTYPE;
BEGIN
FOR MY_REC IN MY_CURSOR(3) LOOP
		MS_OUTPUT.PUT_LINE(MY_REC.TABLE_NAME);
 	ND LOOP;
END;
/
Ouverture du curseur

OPEN EMP_CUR('LEE');
OPEN EMP_CUR('BLAKE', 300);
OPEN EMP_CUR(EMPLOYEE_NAME, 150);
Fermeture du curseur : CLOSE EMP_CUR;
Lecture du curseur (déplacement dans le curseur du début jusque la fin) :

FETCH EMP_CUR INTO MY_EMPNO, MY_ENAME, MY_DEPTNO;

Plusieurs méthodes de scrutations existent.
Méthode 1 : sortie de la boucle par l’instruction exit when

DECLARE CURSOR C1 IS SELECT ENAME, SAL, HIREDATE, JOB FROM EMP;
EMP_REC C1%ROWTYPE; -- DECLARE RECORD VARIABLE THAT REPRESENTS A ROW IN THE EMP TABLE

LOOP
FETCH C1 INTO MY_RECORD;
	EXIT WHEN C1%NOTFOUND; -- Quitter la boucle
-- PROCESS DATA RECORD
END LOOP;

Il est possible de récupérer le “type” du curseur via cur% ROWTYPE

ex :

DECLARE
CURSOR MY_CURSOR IS SELECT SAL + NVL(COMM, 0) WAGES, ENAME FROM EMP;
MY_REC MY_CURSOR%ROWTYPE;
BEGIN
OPEN MY_CURSOR;
LOOP
FETCH MY_CURSOR INTO MY_REC;
		EXIT WHEN MY_CURSOR%NOTFOUND;
		IF MY_REC.WAGES > 2000 THEN
			INSERT INTO TEMP VALUES (NULL, MY_REC.WAGES, MY_REC.ENAME);
		END IF;
	END LOOP;
CLOSE MY_CURSOR;
END;

Il y a positionnement d’indicateurs relatifs au curseur après l’instruction FETCH :
	- deux booléens indiquant si le curseur arrive à récupérer l’enregistrement suivant (cursor%NOTFOUND et %FOUND)
	- le nombre courant d’enregistrements récupérés (cursor%ROWCOUNT)
	- le code erreur de l’erreur oracleSQLCODE (=0 si tout est Ok)
 pour afficher le message d’erreur associé, utiliser le tableau SQLERRM(num_erreur)

Il y a déclenchement de l’exception NO_DATA_FOUND (cette notion sera vu plus loin)
Méthode 2 : la boucle
Il n’y a plus de gestion de l’ouverture, du fecth et de la fermeture du curseur.

Le curseur explicite

emp_rec est du “type” du curseur

BEGIN FOR emp_rec IN c1 LOOP
...
salary_total := salary_total + emp_rec.sal;
END LOOP;

ex :

DECLARE
CURSOR C1 IS SELECT ID, NOM_CLI FROM CLIENT2;
	COUR C1%ROWTYPE;
BEGIN
	FOR COUR IN C1 LOOP
		DBMS_OUTPUT.PUT_LINE(COUR.NOM_CLI||'=>' || C1%ROWCOUNT);
	END LOOP;
END;

Le curseur implicite

BEGIN FOR EMP_REC IN (SELECT ID NOM FROM CLI) LOOP
...
SALARY_TOTAL := SALARY_TOTAL + EMP_REC.SAL;
END LOOP;
Méthode 3 : la gestion des exceptions

La gestion s’effectue par captation des exceptions en particulier l’exception NO_DATA_FOUND.
Les triggers basés
Les triggers basés sont des sous-programmes associés à une table. Ils se déclenclent automatiquement (sauf désactivation) avant ou après une insertion, modification ou destruction ( via le delete). L’une des utilisations est la tracabilité des modifications.

Il convient d’avoir les droits de créer ou modifier les triggers(privilege CREATE TRIGGER).

 Il est possible de créer plusieurs triggers sur une même table.

CREATE TRIGGER AUDIT_SAL
 BEFORE/AFTER UPDATE/INSERT OR DELETE OF SAL (ON EMP)
 FOR EACH ROW
BEGIN
 INSERT INTO EMP_AUDIT VALUES ...
END;

Modification du trigger :

CREATE OR REPLACE TRIGGER AUDIT_SAL
 BEFORE/AFTER UPDATE/INSERT OR DELETE OF SAL (ON EMP)
 FOR EACH ROW
BEGIN
 INSERT INTO EMP_AUDIT VALUES ...
END;

Existence des valeurs précédentes et nouvelles : NEW.chp et :OLD.chp

CREATE TABLE CLI (I INT, NOM VARCHAR(10));
CREATE SEQUENCE SEQ_CLI_PK; -- CREATION D’UNE SEQUENCE A UTILISER AVEC SEQ. NEXTVAL ET SEQ. CURRVAL

CREATE OR REPLACE TRIGGER TR_CLI_PK
BEFORE INSERT ON CLI
	FOR EACH ROW
BEGIN
	SELECT SEQ_CLI_PK.NEXTVAL INTO :NEW.I FROM DUAL;
END;

Destruction du trigger : DROP TRIGGER TRG_CLI_PK;
Celui-ci est détruit si la table est détruite.
Il peut être désactivé via
Tous les triggers rattachées à une table peuvent être désactivés via ALTER TABLE NOM_TABLE DISABLE ALL TRIGGERS et réactivées en remplaçant DISABLE par ENABLE.

La destruction de la table détruit le trigger

CREATE TRIGGER TOTAL_SALARY 
AFTER DELETE OR INSERT OR UPDATE OF DEPTNO, SAL ON EMP FOR EACH ROW
BEGIN 
 IF INSERTING OR DELETING OR (UPDATING AND :OLD.DEPTNO != :NEW.DEPTNO) THEN
UPDATE DEPT SET TOTAL_SAL = TOTAL_SAL - :OLD.SAL WHERE DEPTNO = :OLD.DEPTNO; 
	END IF; 
END; 
Les exceptions
La programmation par exceptions permet de mieux diviser la partie du code nécessaire pour traiter les situations exceptionnelles de celle nécessaire à traiter les cas généraux. C'est une technique assez puissante que l'on rencontre dans de nombreux langages de programmation récents.

Deux types d’exception existent : Les exceptions prédéfinies (ou internes ou systèmes), par exemple la division par zéro, un curseur vide, ... et les exceptions définies par l’utilisateur qui sont déclarées dans la partie DECLARE du bloc. La définition a les mêmes règles de portée que celles des variables (une définition locale surcharge une définition globale).

La gestion des exceptions comprend trois parties :

- la déclaration de l'exception : consiste à nommer l'exception. Les exceptions pré-définies sont définies globalement mais le programmeur peut éventuellement les renommer. Les exceptions définies par le programmeur sont dans la partie DECLARE, 
- le déclenchement de l'exception : les exceptions pré-définies sont déclenchées automatiquement ou explicitement par le programmeur, alors que les exceptions définies par le programmeur sont déclenchées explicitement. Le déclenchement explicite se fait au moyen de l'instruction RAISE nom-exception.
- le traitement de l'exception : les exceptions sont traitées dans le gestionnaire d'exceptions (partie EXCEPTION du bloc PL/SQL). A chaque nom d'exception on associe un traitement (séquence d'opérations PL/SQL). On dispose également du mot clé OTHERS qui permet de récupérer les autres exceptions.

Lorsqu'une exception est déclenchée dans un bloc, on cherche le traitement associé dans le bloc. S'il n'existe pas on va voir l'exception OTHERS et si elle n'est pas présente on cherche dans les blocs englobants (et récursivement). Le traitement d'une exception ne peut revenir dans le bloc qui l'a déclenchée, on revient toujours au bloc englobant. Si le traitement d'une exception déclenche une autre exception, elle est propagée dans le bloc englobant (et récursivement).
 Les exceptions prédéfinies :

EXCEPTION
WHEN ZERO_DIVIDE THEN
		ROLLBACK;
	WHEN VALUE_ERROR THEN
		INSERT INTO ERRORS VALUES ...
		COMMIT;
	WHEN OTHERS THEN -- EXCEPTION POUBELLE
		NULL;
END;

Liste non exhaustive des exceptions prédéfinies

Exception 	Erreur Oracle	Valeur SQLCODE	
CURSOR_ALREADY_OPEN	ORA-06511 	-6511 	
DUP_VAL_ON_INDEX	ORA-00001 	-1 	
NO_DATA-FOUND	ORA-01403 	+100 	
NOT_LOGGED_ON	ORA-01012 	-1012 	
TOO_MANY_ROWS	ORA-01422 	-1422 	
VALUE_ERROR	ORA-06502 	-6502 	
ZERO_DIVIDE	ORA-01476 	-1476 	

Il est possible d’utiliser le code de retour SQLCODE au lieu de “trapper” l’exception par son nom
Les exceptions utilisateurs

Elles sont définies par l’utilisateur.

DECLARE
	COMM_MISSING EXCEPTION; -- DECLARE EXCEPTION
BEGIN
	...
	IF COMMISSION IS NULL THEN
		RAISE COMM_MISSING; -- RAISE EXCEPTION
	ELSE
		BONUS := (SALARY * 0.10) + (COMMISSION * 0.15);
	END IF;

EXCEPTION 
	WHEN COMM_MISSING OR …. THEN
		-- PROCESS ERROR
END;
L’auto indendation de la clé primaire
CREATE SEQUNCE SEQINCPK;

CREATE TRIGGER INCPK
	BEFORE INSERT OF CLI
	FOR EACH ROW
BEGIN
	SELECT SEQINCPK.NEXTAL INTO NEW:ID_CLI FROM DUAL;
END;
L’utilisation d’une fonction PL/SQL dans une requête SQL
SELECT FONCT(ID) FROM CLI;
Le SQL dynamique
Le SQL dynamique consiste à générer le code SQL dans une chaîne de caractères avant de la faire exécuter par le moteur.
Il est possible d’inclure une commande de définition de données.

L’exécution immédiate

Il convient d’utiliser des ‘bind variables’ pour gérer les paramètres

EXECUTE IMMEDIATE dynamic_string
[INTO {define_variable[, define_variable]... | record}]
[USING [IN | OUT | IN OUT] bind_argument
[, [IN | OUT | IN OUT] bind_argument]...]
[{RETURNING | RETURN} INTO bind_argument[, bind_argument]...];

EXECUTE IMMEDIATE 'CREATE TABLE bonus (id NUMBER, amt NUMBER)';

 sql_stmt := 'INSERT INTO dept VALUES (:1, :2, :3)';
 EXECUTE IMMEDIATE sql_stmt USING dept_id, dept_name, location;

Le curseur dynamiqsue

CREATE PROCEDURE DROP_TABLE (TABLE_NAME IN VARCHAR2) AS
	CID INTEGER;
BEGIN
	CID := DBMS_SQL.OPEN_CURSOR; /* OPEN NEW CURSOR AND RETURN CURSOR ID. */
	/* PARSE AND IMMEDIATELY EXECUTE DYNAMIC SQL STATEMENT BUILT BY 
 CONCATENATING TABLE NAME TO DROP TABLE COMMAND. */
	DBMS_SQL.PARSE(CID, 'DROP TABLE ' || TABLE_NAME, DBMS_SQL.V7);
	DBMS_SQL.CLOSE_CURSOR(CID); /* CLOSE CURSOR. */

EXCEPTION
	/* IF AN EXCEPTION IS RAISED, CLOSE CURSOR BEFORE EXITING. */
	WHEN OTHERS THEN
		DBMS_SQL.CLOSE_CURSOR(CID);
END DROP_TABLE;